#![allow(non_snake_case)]
use leptos::{ev, html, prelude::*};
use shq_common::prelude::Size;
use std::collections::HashMap;
use thaw_components::{Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{class_list, ArcOneCallback, ComponentRef};

use crate::{InputPrefix, InputSuffix, ValidType, _binder::Binder, select::tree_items::TreeState, InputRef};
use icondata as i;
use leptos_icons::*;

use super::tree_items::{ExpandMode, TreeItems};

#[component]
pub fn TreeSelect(
    value: Option<String>,
    #[prop(into)] tree_items: StoredValue<TreeItems>,
    #[prop(default=ExpandMode::First, into)] init_expand_mode: ExpandMode,
    #[prop(optional, into)] on_change: Option<ArcOneCallback<Option<String>>>,
    #[prop(optional, into)] placeholder: MaybeProp<String>,
    #[prop(optional, into)] valid_type: MaybeProp<ValidType>,
    #[prop(optional, into)] only_select_leaf: MaybeProp<bool>,
    #[prop(optional, into)] size: MaybeProp<Size>,
    #[prop(optional, into)] icon_map: HashMap<String, Option<i::Icon>>,
    #[prop(optional)] input_prefix: Option<InputPrefix>,
    #[prop(optional)] input_suffix: Option<InputSuffix>,
    #[prop(optional, into)] disabled: MaybeProp<bool>,
    #[prop(default=true)] is_dropdown: bool,
    #[prop(default=true.into(), into)] border: MaybeProp<bool>,
    #[prop(optional)] comp_ref: ComponentRef<InputRef>,
    #[prop(optional)] inner_max_width: u32,
    #[prop(optional)] is_init_focus: bool,
) -> impl IntoView {
    let value = StoredValue::new(value);
    let is_show_menu = RwSignal::new(false);
    let filter_text: RwSignal<Option<String>> = RwSignal::new(None);
    let tree_state = StoredValue::new(
        tree_items
            .with_value(|tis| TreeState::from_single(tis, value.get_value(), &init_expand_mode)),
    );
    let trigger_ref = NodeRef::<html::Label>::new();
    let menu_ref = NodeRef::<html::Div>::new();
    let filter_ref = NodeRef::<html::Input>::new();
    let show_menu = move || {
        is_show_menu.set(true);
        if let Some(input_el) = filter_ref.get_untracked() {
            _ = input_el.focus();
        }
    };
    let pre_sel= tree_state.with_value(|tis| tis.get_pre_selected());
    #[cfg(any(feature = "csr", feature = "hydrate"))]
    {
        use leptos::wasm_bindgen::__rt::IntoJsResult;
        let timer = window_event_listener(ev::click, move |ev| {
            let Some(menu_ref) = menu_ref.get_untracked() else {
                return;
            };
            let Some(trigger_ref) = trigger_ref.get_untracked() else {
                return;
            };
            let el = ev.target();
            let mut el: Option<web_sys::Element> =
                el.into_js_result().map_or(None, |el| Some(el.into()));
            let body = document().body().unwrap();
            while let Some(current_el) = el {
                if current_el == *body {
                    break;
                };
                if current_el == **menu_ref || current_el == **trigger_ref {
                    return;
                }
                el = current_el.parent_element();
            }
            is_show_menu.set(false);
        });
        let on_change = on_change.clone();
        let on_keydown = move |e: ev::KeyboardEvent| {
            let key = e.key();
            if key==*"Enter" || key==*"Escape" || key==*"ArrowUp" || key==*"ArrowDown" {
                e.prevent_default();
                e.stop_propagation();
            }else{
                return;
            }
            if key == *"Enter" {
                if let Some(pre_sel) = pre_sel.get_untracked() {
                    tree_state.with_value(|tis| {
                        tis.set_selected(Some(pre_sel.clone()));
                        on_change.as_ref().map(|cb| cb(Some(pre_sel)));
                    });
                }
                return;
            }else if key == *"Escape" {
                is_show_menu.set(false);
                return;
            }
             tree_state.with_value(|tis| {
                if key == *"ArrowUp" {
                    tis.pre_select_prev();
                } else if key == *"ArrowDown" {
                    tis.pre_select_next();
                } else if key == *"ArrowLeft" ||
                           key == *"ArrowRight" {
                    if let Some(pre_sel) = pre_sel.get_untracked() {
                        if !tis.is_leaf(&pre_sel) {
                            let is_expanded = key == *"ArrowRight";
                            tis.set_expanded(&pre_sel, is_expanded);
                        }
                    }
                }
            });
        };
        let listener = window_event_listener(ev::keydown, move |ev| {
            if !is_show_menu.get() {
                return;
            }
            on_keydown(ev)
        });
        on_cleanup(move || {
            timer.remove();
            listener.remove();
        });
    }
    let select_option_label =
        Memo::new(
            move |_| match tree_state.with_value(|tis| tis.get_selected()) {
                Some(value) => tree_state.with_value(|tis| tis.get_label(&value).to_owned()),
                None => "".to_owned(),
            },
        );
    let label_class = move || {
        if border.get().unwrap_or_default() {
            let class = valid_type.get().unwrap_or_default().as_color_str();
            format!("input input-bordered input-{}", class)
        } else {
            "".to_owned()
        }
    };
    let root_ids =
        move || filter_text.with(|v| tree_state.with_value(|tis| tis.get_root_ids(v.as_deref())));
    let style_width = if inner_max_width > 0 {
        format!("max-width:{}px", inner_max_width)
    } else {
        "".to_owned()
    };
    let input_ref = NodeRef::new();
    input_ref.on_load(move |_| {
        comp_ref.load(InputRef { input_ref });
    });
    Effect::new(
        move || {
            if let Some(input_el) = input_ref.get_untracked() {
                if is_init_focus {
                    _ = input_el.focus();
                }
            }
        }
    );
    let icon_map = StoredValue::new(icon_map);
    let menu_view = move || {
        let on_change = on_change.clone();
        view! {
            <div class="p-2">
                <input type="text" placeholder="筛选" node_ref=filter_ref
                    class="input input-bordered input-sm w-full max-w-xs"
                    on:input=move |ev| {
                        let val = event_target_value(&ev).trim().to_string();
                        if val.is_empty() {
                            filter_text.update(|v|*v=None);
                        }else{
                            filter_text.update(|v|*v=Some(val));
                        }
                }
                />
            </div>
            <ul class="menu">
                {
                    move || {
                        let root_ids = root_ids();
                        let tree_state = tree_state;
                        let filter_text = filter_text;
                        let only_select_leaf = only_select_leaf;
                        let size = size;
                        let on_change = on_change.clone();
                        root_ids.into_iter().map(move |id| {
                            let icon=icon_map.with_value(|m|m.get(&id).map(|i|*i).unwrap_or(None));
                            render_tree(tree_state, id, filter_text, only_select_leaf, icon, size,on_change.clone())
                        }).collect_view()
                    }
                }
            </ul>
        }
    };
    if is_dropdown {
        view! {
            <Binder>
                <label
                    class=class_list!["flex items-center gap-2 w-full max-w-xs",label_class,move||size.get().unwrap_or_default().to_class("input")]
                    node_ref=trigger_ref
                    on:click=move|_|show_menu()
                >
                    {if let Some(sp) = input_prefix.and_then(|p|p.if_.then_some(p)){
                        Some((sp.children)())
                    } else {
                        None
                    }}
                    <input type="text" class=class_list!["grow",size.get().unwrap_or_default().to_class("input")]
                        class=(["outline-0","border-0","w-full","ml-1","mr-1","bg-inherit"],move||!border.get().unwrap_or_default())
                        style=style_width
                        on:focus=move|_|show_menu()
                        placeholder=placeholder.get().unwrap_or_default()
                        readonly=true prop:value=move || select_option_label.get()
                        disabled=move||disabled.get()
                        node_ref=input_ref
                    />
                    <Icon icon=i::AiCaretDownOutlined/>
                    {if let Some(sp) = input_suffix.and_then(|s|s.if_.then_some(s)) {
                        Some((sp.children)())
                    } else {
                        None
                    }}
                </label>
                <Follower
                    slot
                    show=is_show_menu
                    placement=FollowerPlacement::BottomStart
                    width=FollowerWidth::Target
                >
                    <div class="shadow bg-base-100 rounded-box z-10 max-h-72 overflow-auto"
                        node_ref=menu_ref class=("hidden",move||!is_show_menu.get())>
                        {menu_view}
                    </div>
                </Follower>
            </Binder>
        }.into_any()
    } else {
        view! {
            <div class="shadow bg-base-100 rounded-box z-10 max-h-full overflow-auto"
                node_ref=menu_ref>
                {menu_view}
            </div>
        }.into_any()
    }
}

fn render_tree(
    tree_state: StoredValue<TreeState>,
    item_id: String,
    filter_text: RwSignal<Option<String>>,
    only_select_leaf: MaybeProp<bool>,
    icon: Option<i::Icon>,
    size: MaybeProp<Size>,
    on_change: Option<ArcOneCallback<Option<String>>>,
) -> impl IntoView {
    let on_change2 = on_change.clone();
    let item_view = move |item_id: &str| {
        let icon_view = icon.as_ref().map(|i| {
            view! {
                <Icon icon=*i/>
            }
        });
        let sel_id = tree_state.with_value(|tis| tis.get_selected());
        let is_active = sel_id.as_deref() == Some(item_id);
        let label = tree_state.with_value(|tis| tis.get_label(item_id).to_owned());
        let item_id = StoredValue::new(item_id.to_owned());
        let on_change = on_change.clone();
        view! {
            <li>
                <a class=("active",is_active) on:click=move |e|{
                    let item_id = item_id.get_value();
                    e.stop_propagation();
                    tree_state.with_value(|tis| {
                        tis.set_selected(Some(item_id.clone()));
                        on_change.as_ref().map(|cb| cb(Some(item_id)));
                    });
                }>
                    <div class="flex items-center gap-2">
                        {label}
                        {icon_view}
                    </div>
                </a>
            </li>
        }
        .into_any()
    };
    let item_view_with_children = move |item_id: &str, child_ids: Vec<String>| {
        let osl = only_select_leaf.get().unwrap_or_default();
        let is_active = tree_state.with_value(|tis| {
            let sel_id = tis.get_selected();
            sel_id.as_deref() == Some(item_id) && (!osl || tis.is_leaf(item_id))
        });
        let is_expanded =
            tree_state.with_value(|tis| tis.get_expanded(item_id).map(|v| v).unwrap_or_default());
        let label = tree_state.with_value(|tis| tis.get_label(item_id).to_owned());
        let item_id = StoredValue::new(item_id.to_owned());
        let on_change = on_change2.clone();
        let on_change2 = on_change2.clone();
        view! {
            <li>
                <details open=move||is_expanded.get()>
                    <summary class=("active",is_active) on:click=move |e|{
                            e.stop_propagation();
                            tree_state.with_value(|tis| {
                                let on_change = on_change.clone();
                                if !osl {
                                    tis.set_selected(Some(item_id.get_value()));
                                    on_change.as_ref().map(|cb| cb(Some(item_id.get_value())));
                                }
                                let item_id = item_id.get_value();
                                tree_state.with_value(|tis| {
                                    tis.set_expanded(&item_id, !is_expanded.get());
                                });
                            });
                        }>{label}
                    </summary>
                    <ul>
                        {
                            let on_change = on_change2.clone();
                            child_ids.into_iter().map(|id|{
                                render_tree(tree_state,id,filter_text,only_select_leaf,icon,size,on_change.clone())
                            }).collect_view()
                        }
                    </ul>
                </details>
            </li>
        }
        .into_any()
    };
    let child_ids = filter_text
        .with(|f| tree_state.with_value(|tis| tis.get_item_ids_by_pid(&item_id, f.as_deref())));
    if child_ids.is_empty() {
        item_view(&item_id)
    } else {
        item_view_with_children(&item_id, child_ids)
    }
}
